/* !FormText.textfile.c */

#include <ctype.h>
#include <stdio.h>
#include <string.h>

#include "event.h"
#include "flex.h"
#include "saveas.h"

#include "err.h"
#include "hourglass.h"
#include "import.h"
#include "msgtrans.h"
#include "settings.h"
#include "textfile.h"


static char *textfile = 0;  /* Pointer to this is used as handle throughout */

static char *contents = 0;  /* Contents buffer */

static int contents_pages;

static int fill_size;	/* Size of last buffer allocated during RAM import */

/* Whether we have already given truncation warning */
static int truncated;

/* Returns pointer to point at which to split text */
static char *find_split(char *source, int width)
{
  char *last_space = source + width;
  register char *ptr;
  for (ptr = source; ptr < source + width; ptr++)
  {
    if (!*ptr || *ptr == '\n')
      return ptr;
    if (isspace(*ptr))
      last_space = ptr;
  }
  return last_space;
}

#define Int_Split(s, w) (int) find_split((s), (w)) - (int) (s)

/* Inserts a zero-terminated string (excluding terminator) into a flex buffer */
static int insert_string(flex_ptr buffer, int offset, const char *insert)
{
  register int len = strlen(insert);
  if (!len)
    return 1;
  if (!flex_midextend(buffer, offset, len))
    return 0;
  strncpy(((char *) *buffer) + offset, insert, len);
  return 1;
}

#define Ins_InTextFile(o, i) insert_string((flex_ptr) &textfile, (o), (i))
#define Ins_InContents(o, i) insert_string((flex_ptr) &contents, (o), (i))

/* Formats paragraphs to fit width */
static int format_paragraphs(flex_ptr buffer, int width)
{
  register int offset, line_offset;
  int heading = 0;
  for (offset = 0;
  	offset < flex_size(buffer) - 1;
  	offset += line_offset)
  {
    while (((char *) *buffer)[offset] == '\n' && offset < flex_size(buffer) - 1)
      ++offset;
    /* Look out for headings */
    if (((char *) *buffer)[offset] == settings.heading_flag)
    {
      heading = 1;
      offset++;
    }
    else
      heading = 0;
    for (line_offset = 0;
    	line_offset + offset < flex_size(buffer);
    	++line_offset)
    {
      /* Expand any tabs along the way */
      if (((char *) *buffer)[offset + line_offset] == '\t')
      {
        char spaces[8];
        ((char *) *buffer)[offset + line_offset] = ' ';
        strcpy(spaces, "       ");
        if (7 - line_offset % 8)
        {
          spaces[7 - line_offset % 8] = 0;
          if (!insert_string(buffer, offset + line_offset, spaces))
            return 0;
        }
      }
      else if (iscntrl(((char *) *buffer)[offset + line_offset]))
      {
        ++line_offset;
        break;
      }
      if (offset + line_offset >= flex_size(buffer))
        break;
      /* Check for overlong line */
      if (line_offset >= width)
      {
        char *spt = find_split(&((char *) *buffer)[offset], width);
        offset = (int) spt - (int) (*buffer);
        if (isspace(((char *) *buffer)[offset]))
          ((char *) *buffer)[offset] = '\n';
        else if (((char *) *buffer)[offset] != '\n')
        {
          if (!insert_string(buffer, offset, "\n"))
            return 0;
        }
        if (heading)
        {
          char ins[2];
          ins[0] = settings.heading_flag;
          ins[1] = 0;
          if (!insert_string(buffer, offset + 1, ins))
            return 0;
        }
        line_offset = 0;
        break;
      }
    }
  }
  return 1;
}

#define FormPara_TextFile() \
  format_paragraphs((flex_ptr) &textfile, settings.text_width)

static int underline_headings()
{
  register int offset;
  for (offset = 0; offset < flex_size((flex_ptr) &textfile) - 2; offset++)
  {
    if (textfile[offset] == '\n' || !offset)
    {
      if (offset)
        offset++;
      while (textfile[offset] == '\n')
        offset++;
      if (textfile[offset] == settings.heading_flag)
      {
        /* Count length of line */
        register int len;
        char linechars[256];	/* On safe side */
        register int n;
        offset++;	/* Skip flag */
        for (len = 0; textfile[offset + len] != '\n'; len++);
        for (n = 0; n < len; n++)
          linechars[n] = settings.underline_char;
        linechars[len] = '\n';
        linechars[len + 1] = 0;
        if (!Ins_InTextFile(offset + len + 1, linechars))
          return 0;
        offset += 2 * len;
      }
    }
  }
  return 1;
}

/* Inserts a form feed every page length, returning number of pages created */
static int make_page_breaks(flex_ptr buffer)
{
  register int offset;
  register int lines = 0;
  int last_line = 0;
  int pages = 0;
  for (offset = 0; offset < flex_size(buffer); offset++)
  {
    /* Pad short pages to make sure footer will be in right place */
    if (((char *) *buffer)[offset] == '\f' || offset > flex_size(buffer) - 2)
    {
      while (++lines < settings.text_height)
        if (!insert_string(buffer, offset++, "\n"))
          return 0;
      lines = 0;
      ++pages;
    }
    else if (((char *) *buffer)[offset] == '\n')
    {
      if (++lines >= settings.text_height)
      {
        /* Avoid splitting headings from their underlines */
        if (((char *) *buffer)[offset + 1] == settings.underline_char)
        {
          offset = last_line;
          if (!insert_string(buffer, offset, "\n"))
            return 0;
        }
        if (!insert_string(buffer, offset + 1, "\f"))
          return 0;
        lines = 0;
        ++offset;
        ++pages;
      }
      last_line = offset;
    }
  }
  return pages;
}

#define PgBrk_TextFile() make_page_breaks((flex_ptr) &textfile)
#define PgBrk_Contents() make_page_breaks((flex_ptr) &contents)

/* Inserts substr in buffer, replacing a pair of flag chars eg %p
   lenptr => buffer's length, which is updated
   *offset is also updated to avoid expanding inserted strings
*/
static void insert_in_header(char *buffer, int *offset, const char *substr,
	int *lenptr)
{
  /* Note number of bytes moved is *lenptr - (*offset+1); includes terminator */
  register int len = strlen(substr);
  switch (len)
  {
    case 0:
      memmove(buffer + *offset, buffer + *offset + 2,
      	*lenptr - (*offset + 1));
      *lenptr -= 2;
      break;
    case 1:
      buffer[*offset] = substr[0];
      memmove(buffer + *offset + 1, buffer + *offset + 2,
      	((*lenptr)--) - (*offset + 1));
      break;
    case 2:
      buffer[*offset] = substr[0];
      buffer[++(*offset)] = substr[1];
      break;
    default:
      memmove(buffer + *offset + 2 + len - 2, buffer + *offset + 2,
      	*lenptr - (*offset + 1));
      *lenptr += len - 2;
      strncpy(buffer + *offset, substr, len);	/* Excluding terminator */
      *offset += len - 1;
      break;
  }
}

/* 'Detokenises' string in buffer, given page and heading
   to make a formatted header/footer.
   No check for buffer overflow, so be careful.
   Restricts number of lines to lines, returns number of lines
*/

static int make_header(char *buffer, int page, int lines)
{
  char thead[256];		/* Zero-terminated heading */
  int offset;
  register int lns = 1;
  int len = strlen(buffer);	/* Updated as we go along */
  thead[0] = ' ';
  thead[1] = 0;
  for (offset = 0; offset < len; offset++)
  {
    switch (buffer[offset])
    {
      case '|':
        switch (toupper(buffer[offset + 1]))
        {
          case 'M':
            insert_in_header(buffer, &offset, "\n", &len);
            break;
          case '|':
            insert_in_header(buffer, &offset, "|", &len);
            break;
        }
        break;
      case '%':
        switch (tolower(buffer[offset + 1]))
        {
          char pstr[8];
          case 'p':
            sprintf(pstr, "%d", page);
            insert_in_header(buffer, &offset, pstr, &len);
            break;
          case '%':
            insert_in_header(buffer, &offset, "%", &len);
            break;
        }
        break;
    }
  }
  /* Split into formatted lines */
  offset = 0;
  while (offset < len)
  {
    char *sp = find_split(buffer + offset, settings.text_width);
    if (!*sp)
      break;
    if (*sp != '\n' && *sp != ' ')
      memmove(sp + 1, sp, ++len - ((int) sp - (int) buffer));
    *sp = '\n';
    if (lns++ > lines)
    {
      *(sp + 1) = 0;
      truncated = 1;
      return lns - 1;
    }
    offset = (int) sp - (int) buffer + 1;
  }
  return lns;
}

/* hd is NZ for headers, 0 for footers */
static int add_headers(int hd)
{
  register int offset;
  int page = 1;
  int lastpage = 0;
  for (offset = 0; offset < flex_size((flex_ptr) &textfile); offset++)
  {
    /* Find beginning of next page */
    if (!hd && offset == flex_size((flex_ptr) &textfile) - 1)
      lastpage = 1;
    if ((hd && !offset) || textfile[offset] == '\f' || lastpage)
    {
      char buffer[300];
      int lines;
      /* Add lines above header/footer */
      for (lines = 0;
      	lines < (hd ? settings.top_margin : settings.bottom_margin);
      	lines++)
        buffer[lines] = '\n';
      if (page > contents_pages)
        strcpy(buffer + lines, (hd ? settings.header : settings.footer));
      else
      {
        if (hd)
          strcpy(buffer + lines, settings.contents_title);
        else
          buffer[lines] = 0;
      }
      lines = make_header(buffer + lines, (page++) - contents_pages,
      	(hd ? settings.header_height : settings.footer_height) - 1);
      		/* height - 1 to avoid header running into text */
      /* Add lines below header/footer */
      if (!lastpage)
        for (; lines <= (hd ? settings.header_height : settings.footer_height);
        	lines++)
          strcat(buffer, "\n");
      if (hd && offset != 0)
        offset++;
      if (!Ins_InTextFile(offset, buffer))
        return 0;
      offset += strlen(buffer);
    }
  }
  return 1;
}

static int add_left_margin()
{
  register int offset;
  char margin[256];
  for (offset = 0; offset < settings.left_margin; offset++)
    margin[offset] = ' ';
  margin[offset] = 0;
  for (offset = 0; offset < flex_size((flex_ptr) &textfile) - 2; offset++)
  {
    if (textfile[offset] == '\n' || !offset)
    {
      if (offset)
        offset++;
      while (textfile[offset] == '\n')
        offset++;
      /* Look out for page break */
      if (textfile[offset] == '\f')
        offset++;
      if (!Ins_InTextFile(offset, margin))
        return 0;
    }
  }
  return 1;
}

/* Adds an entry to contents, consisting of string starting at offset
   in textfile; returns new offset */
static int add_content(int offset, int *page)
{
  register int tlen;
  char *ln;
  char numstr[8];
  char cl[2];
  int coffset = flex_size((flex_ptr) &contents) - 1;
  int pg = *page;
  do
  {
    int loffset = flex_size((flex_ptr) &contents) - 1;
    /* Find length of current line of this heading */
    for (tlen = 0; textfile[offset + tlen] != '\n'; tlen++);
    /* Add this line to contents */
    if (!flex_extend((flex_ptr) &contents, tlen + loffset + 2))
      return 0;
    strncpy(contents + loffset, textfile + offset, tlen);
    contents[loffset + tlen] = '\n';
    contents[loffset + tlen + 1] = 0;
    /* Move on to char following \n after underscore... */
    offset += tlen * 2 + 2;
    if (textfile[offset] == '\f')
    {
      ++offset;
      ++(*page);
    }
    if (textfile[offset] == settings.heading_flag)
    {
      contents[loffset + tlen] = ' ';
    }
  } while (textfile[offset++] == settings.heading_flag);
  	/* ...to treat multi-line headings as one entry */
  /* Format to correct width */
  while (ln = find_split(contents + coffset, settings.text_width - 8),
  	*ln != '\n')
  {
    coffset = (int) ln - (int) contents;
    if (*ln == ' ')
      *ln = '\n';
    else
    {
      Ins_InContents(coffset, "\n");
      ++coffset;
    }
    ++coffset;
  }
  /* Add page number */
  sprintf(numstr, "%d", pg);
  tlen = (settings.text_width - strlen(numstr)) -
  	((int) ln - (int) contents - coffset);
  cl[0] = settings.contents_leader;
  cl[1] = 0;
  for (; tlen; --tlen)
  {
    if (!Ins_InContents(flex_size((flex_ptr) &contents) - 2, cl))
      return 0;
  }
  if (!Ins_InContents(flex_size((flex_ptr) &contents) - 2, numstr))
    return 0;
  return offset;
}

static int generate_contents()
{
  register int offset;
  int size = flex_size((flex_ptr) &textfile);
  int page = 1;
  if (!flex_alloc((flex_ptr) &contents, 1))
    return 0;
  contents[0] = 0;
  for (offset = 0; offset < size; offset++)
  {
    if (textfile[offset] == settings.heading_flag)
    {
      offset = add_content(offset + 1, &page) - 1;
      if (!offset)
        return 0;
    }
    else if (textfile[offset] == '\f')
      ++page;
  }
  contents_pages = PgBrk_Contents();
  if (!Ins_InContents(size = strlen(contents), "\f"))
    return 0;
  if (!flex_midextend((flex_ptr) &textfile, 0, ++size))
    return 0;
  strncpy(textfile, contents, size);
  return 1;
}

/* Remove heading flags */
static int remove_flags()
{
  register int offset;
  for (offset = 0; offset < flex_size((flex_ptr) &textfile); offset++)
  {
    if (textfile[offset] == settings.heading_flag &&
    	(!offset || textfile[offset - 1]=='\n' || textfile[offset - 1]=='\f'))
    {
      if (!flex_midextend((flex_ptr) &textfile, offset + 1, -1))
        return 0;
    }
  }
  return 1;
}

int textfile_process(ObjectId saveas_id)
{
  int stage = 0;
  int stages = 3;
  int ok = 1;
  truncated = 0;
  hourglass_on();
  contents_pages = 0;
  /* Count stages we'll be doing */
  if (settings.underline_char)
    ++stages;
  if (settings.left_margin)
    ++stages;
  if (settings.header_height || settings.top_margin)
    ++stages;
  if (settings.footer_height || settings.bottom_margin)
    ++stages;
  if (settings.generate_contents)
    ++stages;
  /* Terminate last line */
  if (textfile[flex_size((flex_ptr) &textfile) - 1] != '\n')
    ok = flex_extend((flex_ptr) &textfile, flex_size((flex_ptr) &textfile) + 1);
  if (ok)
  {
    textfile[flex_size((flex_ptr) &textfile) - 1] = '\n';
    ok = FormPara_TextFile();
  }
  hourglass_percentage((++stage * 100) / stages);
  if (ok && settings.underline_char)
  {
    ok = underline_headings();
    hourglass_percentage((++stage * 100) / stages);
  }
  if (ok)
  {
    ok = PgBrk_TextFile();
    hourglass_percentage((++stage * 100) / stages);
  }
  if (ok && settings.generate_contents)
  {
    ok = generate_contents();
    hourglass_percentage((++stage * 100) / stages);
  }
  if (ok && settings.header_height || settings.top_margin)
  {
    ok = add_headers(1);
    hourglass_percentage((++stage * 100) / stages);
  }
/* err_report(0, "Added headers"); */
  if (ok && settings.footer_height || settings.bottom_margin)
  {
    ok = add_headers(0);
    hourglass_percentage((++stage * 100) / stages);
  }
/* err_report(0, "Added footers"); */
  if (ok)
  {
    ok = remove_flags();
    hourglass_percentage((++stage * 100) / stages);
  }
/* err_report(0, "Removed flags"); */
  if (ok && settings.left_margin)
    ok = add_left_margin();
/* err_report(0, "Added left margin"); */
  hourglass_off();
  if (ok)
  {
    if (truncated)
      err_report(0, msgs_lookup("HeadChop"));
    E(saveas_set_data_address(0, saveas_id,
    	textfile, flex_size((flex_ptr) &textfile),
    	0, 0));
    return 1;
  }
  else
  {
    err_complain(0, msgs_lookup("Mem"));
    return 0;
  }
}

static int textfile_loader(const char *filename, void *handle)
{
  _kernel_osfile_block kob;
  int size;
  int result;
  /* Find size of file */
  _kernel_osfile(17, filename, &kob);
  size = kob.start;
  /* Allocate memory for it */
  if (!flex_alloc(handle, size))
    return 0;
  /* Load it into allocated buffer */
  kob.load = (int) textfile;
  kob.exec = 0;
  result = _kernel_osfile(16, filename, &kob);
  if (result != 1)
  {
    /* Report suitable error and release memory */
    if (result > 255)
      E((_kernel_oserror *) result);
    else
    {
      kob.load = result;
      E((_kernel_oserror *) _kernel_osfile(19, filename, &kob));
    }
    flex_free(handle);
    return 0;
  }
  return size;
}

static int textfile_bufferer(void **bufptr, int size, void *handle)
{
  if (!*bufptr)
  {
    if (!flex_alloc(handle, size))
      return 0;
    fill_size = size;
    *bufptr = textfile;
  }
  else
  {
    if (size < fill_size)
      fill_size = 0;
    else
    {
      int oldsize = flex_size(handle);
      if (!flex_extend(handle, (fill_size = ArbitrarySize) + oldsize))
        fill_size = 0;
      else
        *bufptr = textfile + oldsize;
    }
  }
  return fill_size;
}

static void textfile_complete(const char *filename, int size, void *handle)
{
  if (size <= 0)
  {
    textfile_clear();
    return;
  }
  if (flex_size(handle) != size)
    flex_extend(handle, size);
  settings_show();
}

static int textfile_importer(WimpMessage *message, void *handle)
{
  if (message->data.data_load.file_type == FileType_Text)
  {
    textfile_clear();
    import_start(message, textfile_loader, textfile_bufferer, textfile_complete,
      	handle);
  }
  return 1;
}

void textfile_initialise()
{
  flex_init((char *) msgs_lookup("_TaskName"), NULL, 0);
  EF(event_register_message_handler(Wimp_MDataLoad,
  	textfile_importer, &textfile));
  EF(event_register_message_handler(Wimp_MDataSave,
  	textfile_importer, &textfile));
}

void textfile_clear()
{
  if (contents)
    flex_free((flex_ptr) &contents);
  if (textfile)
    flex_free((flex_ptr) &textfile);
}
